Создание документа типа X, на основе данных, полученных при работе юзера с документом типа Y, в MFC Multiple Documents приложении и дальнейший обмен между этими документами данными, внятно не описывается ни в одной книге по MFC программированию. В жизни же, обычно, требуется именно это.
К примеру создать документ-отчет или документ-график, на основе данных другого документа, который является электронной таблицей, базой данных - да бог ведает чем.
В принципе подобную задачу можно решить на основе так называемого MVSD (Multiple Views Single Document) приложения, когда к одному документу относятся несколько видов (разного естественно типа). См. View Management.
Допустим мы в виде X наколотили какие нибудь данные, в виде Y рисуем на основе этих данных график, а в виде Z - отчет. И вид X и вид Y и вид Z берут данные из одного и того же документа. Вроде все довольны. Но. MVSD - техника очень хорошая, но к сожалению, только до того времени пока не приходится сохранять содержимое какого нибудь производного вида. Для сохранения содержимого производного вида обычно в классе этого вида приходится делать CYView::OnFileSaveAsY(). В случае же когда нам (не дай бог) нужно считывать данные этого вида из файла и на их основе инициализировать данные нашего единственного документа – такое может довести кого угодно до белого каления. Особенно когда вид должен сохранятся в графическом каком нибудь формате (WMF к примеру – ну не сковородец ли?).
Выход в таком случае один – использовать несколько типов документов, и пускай каждый сам себя сохраняет и грузит. Но прикол MFC состоит в том, что по-умолчанию, в многодокументном приложении, документ типа X ничерта не знает о документе типа Y. И приходится бедному программеру лезть в дебри создания документа, рамки и вида. Не волнуйтесь и не надейтесь – я не стану тут этого описывать – тем более что есть книга Фрэнка Крокета «MFC Мастерская разработчика». Она дает ответы на многие вопросы, но не на наш текущий – как шустро и понятно создавать один документ на основе другого и в дальнейшем обмениваться между ними данными?
Один из ответов – использование библиотеки Objective Chart от фирмы Stingray. Вернее очень маленькой, но очень важной ее части, которая прекрасно живет без самого Objective Chart. Это 4 небольших файла: ComDoc.h и ComDoc.cpp, DocMngr.h и DocMngr.cpp. В нашем нижеследующем примере эти файлы немножко переименованы и немножко (непринципиально) подредактированны. Библиотека хорошая, отдается в исходниках, но за деньги L. Конечно, то что я привожу здесь кусок ее кода – не есть хорошо. Вообще - «Тот, у кого в компе весь софт лицензионный – пусть первый бросит в меня процессор!»
1. Делаем MFC Multiple Documents приложение. С названием к примеру Foo. В нем у нас получился вид CFooView (допустим CFormView–based) и CFooDoc документ.
2. Добавляем к этому приложению еще один тип (шаблон) документа. Можно создать все это по-кускам – отдельно создаем документ, и вид, и строку-шаблон, и меню, и иконки – но это для мазохистов. Гораздо проще сделать еще один MFC Multiple Documents проект, назовем его Bar. И выдерем из него все вышеуказанное. В результате должно получится приложение, которое при запуске спрашивает вопрос : какой документ хотите видеть? И при нажатии кнопки ID_FILE_NEW – та же история.
3. Кидаем в папку проекта файлы SECComDoc.h SECComDoc.cpp SECDocManager.h SECDocManager.cpp и добавляем эти файлы в проект.
4. Вставляем в StdAfx.h ближе книзу
#include "SECComDoc.h" #include "SECDocManager.h"
5. В CFooApp определяем следующие функции:
//файл Foo.h
virtual CDocument* OnFileNew(LPCSTR DocIdent, UINT nOpCode, UINT nSubCode,DWORD dwData, SECComDoc *pParent);
virtual void OnFileNew();
virtual void AddDocTemplate(CDocTemplate* pTemplate);
//файл Foo.cpp
void CFooApp::AddDocTemplate(CDocTemplate* pTemplate)
{
if (m_pDocManager == NULL)
m_pDocManager = new SECDocManager;
m_pDocManager->AddDocTemplate(pTemplate);
}
void CFooApp::OnFileNew()
{
if (m_pDocManager != NULL)
((SECDocManager *)m_pDocManager)->OnFileNew("Foo",0,0,0l,NULL); // May be only One!
}
CDocument *CFooApp::OnFileNew(LPCSTR DocIdent,UINT nOpCode, UINT nSubCode, DWORD dwData, SECComDoc *pParent)
{
if (m_pDocManager != NULL)
return ((SECDocManager*)m_pDocManager)->OnFileNew(DocIdent,nOpCode, nSubCode, dwData, pParent);
return NULL;
}
//В том же файле (вверху) после
BEGIN_MESSAGE_MAP(CFooApp, CWinApp)
// Вместо ON_COMMAND(ID_FILE_NEW, CWinApp::OnFileNew)
ON_COMMAND(ID_FILE_NEW, OnFileNew)
//В том же файле в CFooApp::InitInstance()
// Это – есть в проекте по-умолчанию
pDocTemplate = new CMultiDocTemplate(
IDR_FOOTYPE,
RUNTIME_CLASS(CFooDoc),
RUNTIME_CLASS(CChildFrame),
RUNTIME_CLASS(CFooView));
AddDocTemplate(pDocTemplate);
////////////////////////////////////////////////////////
// Шаблон второго документа – выдранный (вместе с нужными файлами и ресурсами) из проекта Bar
pDocTemplate = new CMultiDocTemplate(
IDR_BARTYPE,
RUNTIME_CLASS(CBarDoc),
RUNTIME_CLASS(CChildFrame),
RUNTIME_CLASS(CBarView));
AddDocTemplate(pDocTemplate);
// Дабы по-умолчанию создавался только один тип документа (это опционально)
if(cmdInfo.m_nShellCommand == CCommandLineInfo::FileNew)
{
if(OnFileNew("Foo", 0, 0, 0l, NULL) == NULL)
return FALSE;
}
else
{
if (!ProcessShellCommand(cmdInfo))
return FALSE;
}
6. В нашем приложении есть два типа документа CFooDoc и CBarDoc - мы должны в файлах где эти классы декларированы и дефинированы заменить везде (можно нажать Ctrl+H и далее Replace All) CDocument на SECComDoc.
7. Перегрузить в обоих наших документах следующие защищенные виртуальные функции
//Декларации: protected: virtual BOOL SecDocUpdate(UINT nSubCode, DWORD dwData); virtual BOOL ProcessUserCommand(UINT nOpCode, UINT nSubCode, UINT dwData); virtual BOOL ClipboardText(void); virtual BOOL GmemInit(UINT code, HANDLE hMem); //А в дефинициях можно просто пока везде вернуть TRUE
8. Все! Ничего полезного прога еще не делает – но поставить компилироватся уже можно.
9. Теперь можно и занятся собственно задачей – создаем из документа CFooDoc документ CBarDoc
// Создаем документ другого типа и инициализируем его данными из текущего
void CFooDoc::OnWorkCreateBarDocument() // Это обработчик пункта меню или кнопки – как угодно
{
CDocument *OnFileNew(LPCTSTR DocIdent,UINT nOpCode, UINT nSubCode,
DWORD dwData,SECComDoc pParent);
CBarDoc *pDoc = (CBarDoc*) ((CFooApp*)AfxGetApp())->OnFileNew("Bar",
SEC_GMEM_INIT, 0, (DWORD)&m_Data, this);
/*
LPCTSTR DocIdent в нашем случае == "Bar" это второй параметр==(CDocTemplate::fileNewName) строки- шаблона для документа CBarDoc
UINT nOpCode может принимать нижеследюущие значения:
SEC_NOOP - юзается когда надо просто создать документ такого типа и ничем не инициализировать его данные
SEC_CLIPBOARD_TEXT - означает что в создающемся CBarDoc вызовется функция ClipboardText()
SEC_GMEM_INIT - означает что в создающемся CBarDoc вызовется функция GmemInit(nSubCode, (HANDLE)dwData)
SEC_DOC_UPDATE - означает что в создающемся CBarDoc вызовется функция SecDocUpdate(nSubCode, dwData);
в принципе - разницы между GmemInit(nSubCode, (HANDLE)dwData) и SecDocUpdate(nSubCode, dwData); никакой
SEC_USER_COMMAND - означает что в создающемся CBarDoc вызовется функция ProcessUserCommand(nOpCode, nSubCode, dwData)
UINT nSubCode - туда можно кидать что душе угодно
DWORD dwData - туда обычно кидают указатель на какую-нибудь структуру с (инициализирующими или просто) данными
SECComDoc *pParent - в нашем случае = this,
благодаря чему создавшийся CBarDoc будет знать своего создателя и сможет до него добраться путем ->m_pParent
*/
}
Чтобы принять инициализирующие данные через SEC_GMEM_INIT в CBarDoc должна быть переопределена была
BOOL CBarDoc::GmemInit(UINT code, HANDLE hMem) // примерно так
{
if(hMem)
{
CFooData *p = reinterpret_cast (hMem);
m_Data = *p; // предполагается что у CFooData есть оператор=
}
return TRUE;
}
10. А теперт финт ушами в другом направлении – из созданного выше CBarDoc шлем данные в связанный с ним (создавший его) CFooDoc
void CBarDoc::OnWorkUpdateFooDocument() // Это обработчик пункта меню или кнопки – как угодно
{
m_Data.m_Age = 40;
m_Data.m_Name = "Shtirlitz";
UpdateParentDocument(SEC_DOC_UPDATE, (DWORD)&m_Data);
}
Чтобы в свою очередь CFooDoc мог принять данные послатые в него посредством UpdateParentDocument(SEC_DOC_UPDATE ......) в нем надо переопределить:
BOOL CFooDoc::SecDocUpdate(UINT nSubCode, DWORD dwData)
{
if(dwData)
{
CFooData *p = reinterpret_cast (dwData);
m_Data = *p; // предполагается что у CFooData есть оператор=
UpdateAllViews(NULL, DATA_TO_FORM);
}
return TRUE;
}
Уф! Все! Смотрите исходники.
Шаг прислал Bulat.